use crate::db::dao::network_acl_rule::NetworkAclRuleDao;
use crate::db::{dao, po};
use crate::dto;
use crate::dto::core::network_acl_rule::NetworkAclRuleDirection;
use crate::service::Error;
use sea_orm::IntoSimpleExpr;
use std::sync::Arc;

#[async_trait::async_trait]
pub trait NetworkAclRuleService: Sync + Send {
    async fn add_ingress_rules(
        &self,
        network_acl_id: &str,
        rules: &[dto::core::network_acl_rule::AddParams],
    ) -> Result<(), Error>;

    async fn add_egress_rules(
        &self,
        network_acl_id: &str,
        rules: &[dto::core::network_acl_rule::AddParams],
    ) -> Result<(), Error>;

    async fn update(
        &self,
        network_acl_rule_id: &str,
        params: &dto::core::network_acl_rule::UpdateParams,
    ) -> Result<(), Error>;

    async fn set_ingress_rules(
        &self,
        network_acl_id: &str,
        rules: &[dto::core::network_acl_rule::AddParams],
    ) -> Result<(), Error>;

    async fn set_egress_rules(
        &self,
        network_acl_id: &str,
        rules: &[dto::core::network_acl_rule::AddParams],
    ) -> Result<(), Error>;

    async fn all_ingress_rules(
        &self,
        network_acl_id: &str,
    ) -> Result<Vec<dto::core::network_acl_rule::NetworkAclRuleItem>, Error>;

    async fn all_egress_rules(
        &self,
        network_acl_id: &str,
    ) -> Result<Vec<dto::core::network_acl_rule::NetworkAclRuleItem>, Error>;

    async fn delete_by_network_acl_id(&self, network_acl_id: &str) -> Result<(), Error>;
}

pub struct DefaultNetworkAclRuleService {
    network_acl_rule_dao: Arc<dyn NetworkAclRuleDao>,
}

impl DefaultNetworkAclRuleService {
    pub fn new(network_acl_rule_dao: Arc<dyn NetworkAclRuleDao>) -> Self {
        DefaultNetworkAclRuleService {
            network_acl_rule_dao,
        }
    }

    async fn add_rules(
        &self,
        direction: NetworkAclRuleDirection,
        network_acl_id: &str,
        rules: &[dto::core::network_acl_rule::AddParams],
    ) -> Result<(), Error> {
        for (i, params) in rules.iter().enumerate() {
            if i >= wanmesh_common::net::acl::ACL_SINGLE_DIRECTION_RULE_LIST_LENGTH_MAX {
                break;
            }

            let network_acl_rule = wanmesh_common::util::new_id()?;

            let protocol = match &params.protocol {
                Some(protocol) => Some(protocol.as_u32()),
                None => None,
            };

            self.network_acl_rule_dao
                .insert(po::network_acl_rule::ActiveModel {
                    id: Default::default(),
                    network_acl_rule_id: po::ActiveValue::Set(network_acl_rule),
                    network_acl_id: po::ActiveValue::Set(network_acl_id.to_string()),
                    index: po::ActiveValue::Set(i as u32),
                    description: po::ActiveValue::Set(params.description.clone()),
                    protocol: po::ActiveValue::Set(protocol),
                    direction: po::ActiveValue::Set(direction.as_u32()),
                    action: po::ActiveValue::Set(params.action.as_u32()),
                    src_ip_address: po::ActiveValue::Set(params.src_ip_address.address().into()),
                    src_ip_prefix_length: po::ActiveValue::Set(
                        params.src_ip_address.network_length(),
                    ),
                    dst_ip_address: po::ActiveValue::Set(params.dst_ip_address.address().into()),
                    dst_ip_prefix_length: po::ActiveValue::Set(
                        params.dst_ip_address.network_length(),
                    ),
                    create_timestamp_millis: Default::default(),
                    update_timestamp_millis: Default::default(),
                })
                .await?;
        }

        Ok(())
    }

    async fn set_rules(
        &self,
        direction: NetworkAclRuleDirection,
        network_acl_id: &str,
        rules: &[dto::core::network_acl_rule::AddParams],
    ) -> Result<(), Error> {
        self.delete_rules(direction.clone(), network_acl_id).await?;

        if rules.is_empty() {
            return Ok(());
        }

        self.add_rules(direction, network_acl_id, rules).await?;

        Ok(())
    }

    async fn rules(
        &self,
        direction: NetworkAclRuleDirection,
        network_acl_id: &str,
    ) -> Result<Vec<dto::core::network_acl_rule::NetworkAclRuleItem>, Error> {
        let filter = dao::network_acl_rule::Filter {
            network_acl_rule_id: None,
            network_acl_id: Some(network_acl_id.to_string()),
            index: None,
            direction: Some(direction.as_u32()),
        };

        let po_list = self
            .network_acl_rule_dao
            .find(dao::FindParams {
                filter,
                limit: Some(
                    wanmesh_common::net::acl::ACL_SINGLE_DIRECTION_RULE_LIST_LENGTH_MAX as u64,
                ),
                order_by: Some(dao::OrderBy {
                    column: po::network_acl_rule::Column::Index.into_simple_expr(),
                    order: sea_orm::Order::Asc,
                }),
                ..Default::default()
            })
            .await?;

        let list = wanmesh_common::util::slice_try_to(&po_list)?;

        Ok(list)
    }

    async fn delete_rules(
        &self,
        direction: NetworkAclRuleDirection,
        network_acl_id: &str,
    ) -> Result<(), Error> {
        self.network_acl_rule_dao
            .delete(dao::network_acl_rule::Filter {
                network_acl_rule_id: None,
                network_acl_id: Some(network_acl_id.to_string()),
                index: None,
                direction: Some(direction.as_u32()),
            })
            .await?;

        Ok(())
    }
}

#[async_trait::async_trait]
impl NetworkAclRuleService for DefaultNetworkAclRuleService {
    async fn add_ingress_rules(
        &self,
        network_acl_id: &str,
        rules: &[dto::core::network_acl_rule::AddParams],
    ) -> Result<(), Error> {
        self.add_rules(NetworkAclRuleDirection::Ingress, network_acl_id, rules)
            .await
    }

    async fn add_egress_rules(
        &self,
        network_acl_id: &str,
        rules: &[dto::core::network_acl_rule::AddParams],
    ) -> Result<(), Error> {
        self.add_rules(NetworkAclRuleDirection::Egress, network_acl_id, rules)
            .await
    }

    async fn update(
        &self,
        network_acl_rule_id: &str,
        params: &dto::core::network_acl_rule::UpdateParams,
    ) -> Result<(), Error> {
        if params == &Default::default() {
            return Ok(());
        }

        let filter = dao::network_acl_rule::Filter {
            network_acl_rule_id: Some(network_acl_rule_id.to_string()),
            ..Default::default()
        };

        self.network_acl_rule_dao
            .update(filter, params.into())
            .await?;

        Ok(())
    }

    async fn set_ingress_rules(
        &self,
        network_acl_id: &str,
        rules: &[dto::core::network_acl_rule::AddParams],
    ) -> Result<(), Error> {
        self.set_rules(NetworkAclRuleDirection::Ingress, network_acl_id, rules)
            .await
    }

    async fn set_egress_rules(
        &self,
        network_acl_id: &str,
        rules: &[dto::core::network_acl_rule::AddParams],
    ) -> Result<(), Error> {
        self.set_rules(NetworkAclRuleDirection::Egress, network_acl_id, rules)
            .await
    }

    async fn all_ingress_rules(
        &self,
        network_acl_id: &str,
    ) -> Result<Vec<dto::core::network_acl_rule::NetworkAclRuleItem>, Error> {
        self.rules(NetworkAclRuleDirection::Ingress, network_acl_id)
            .await
    }

    async fn all_egress_rules(
        &self,
        network_acl_id: &str,
    ) -> Result<Vec<dto::core::network_acl_rule::NetworkAclRuleItem>, Error> {
        self.rules(NetworkAclRuleDirection::Egress, network_acl_id)
            .await
    }

    async fn delete_by_network_acl_id(&self, network_acl_id: &str) -> Result<(), Error> {
        self.network_acl_rule_dao
            .delete(dao::network_acl_rule::Filter {
                network_acl_id: Some(network_acl_id.to_string()),
                ..Default::default()
            })
            .await?;

        Ok(())
    }
}

impl From<&dto::core::network_acl_rule::UpdateParams> for dao::network_acl_rule::UpdateParams {
    fn from(value: &dto::core::network_acl_rule::UpdateParams) -> Self {
        let (src_ip_address, src_ip_prefix_length) = match &value.src_ip_address {
            Some(v) => (
                po::ActiveValue::Set(v.address().into()),
                po::ActiveValue::Set(v.network_length()),
            ),
            None => (po::ActiveValue::NotSet, po::ActiveValue::NotSet),
        };

        let (dst_ip_address, dst_ip_prefix_length) = match &value.dst_ip_address {
            Some(v) => (
                po::ActiveValue::Set(v.address().into()),
                po::ActiveValue::Set(v.network_length()),
            ),
            None => (po::ActiveValue::NotSet, po::ActiveValue::NotSet),
        };
        Self {
            description: po::option_to_active_value(value.description.clone()),
            protocol: po::nullable_option_to_active_value(value.protocol.clone()),
            action: po::option_to_active_value(value.action.clone()),
            src_ip_address,
            src_ip_prefix_length,
            dst_ip_address,
            dst_ip_prefix_length,
        }
    }
}

impl From<dto::core::network_acl_rule::UpdateParams> for dao::network_acl_rule::UpdateParams {
    fn from(value: dto::core::network_acl_rule::UpdateParams) -> Self {
        Self::from(&value)
    }
}

impl TryFrom<&po::network_acl_rule::Model> for dto::core::network_acl_rule::NetworkAclRuleItem {
    type Error = anyhow::Error;

    fn try_from(value: &po::network_acl_rule::Model) -> Result<Self, Self::Error> {
        let protocol = match value.protocol {
            Some(v) => Some(v.try_into()?),
            None => None,
        };
        let src_ip_address = cidr::IpInet::new(
            std::net::IpAddr::try_from(&value.src_ip_address)?,
            value.src_ip_prefix_length,
        )
        .map_err(anyhow::Error::new)?;

        let dst_ip_address = cidr::IpInet::new(
            std::net::IpAddr::try_from(&value.dst_ip_address)?,
            value.dst_ip_prefix_length,
        )
        .map_err(anyhow::Error::new)?;

        Ok(Self {
            network_acl_rule_id: value.network_acl_rule_id.clone(),
            inner: dto::core::network_acl_rule::NetworkAclRule {
                description: value.description.clone(),
                protocol,
                action: value.action.try_into()?,
                src_ip_address,
                dst_ip_address,
            },
        })
    }
}
